﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infraestruturex.BaseLibrary;

namespace Compilex.Syntatic.Declarations
{
    public class BooleanExpression
    {
        public SyntaticContext Context { get; set; }
        public ArithmeticExpression ArithmeticExpression { get; set; }
        public SymbolType Type { get; set; }
        private SymbolType OperandType { get; set; }
        private SimpleStack<BooleanOperand> OperandStack{ get; set; }
        private SimpleStack<string> OperatorStack { get; set; }

        private string debugString = "";

        public BooleanExpression(SyntaticContext context)
        {
            this.Context = context;
            this.ArithmeticExpression = new ArithmeticExpression(context);
            this.OperatorStack = new SimpleStack<string>();
            this.OperandStack = new SimpleStack<BooleanOperand>();
        }

        public void AddSimpleIdOperand()
        {
            this.ArithmeticExpression.AddSimpleIdOperand();
        }

        public void AddArrayElementOperand()
        {
            this.ArithmeticExpression.AddArrayElementOperand();
        }

        public void AddConstantOperand()
        {
            this.ArithmeticExpression.AddConstantOperand();
        }

        public void AddArithmeticOperator()
        {
            this.ArithmeticExpression.AddOperator();
        }

        public void ArithmeticExpressionEnded()
        {
            ArithmeticExpression.ExpressionEnded();
            if (this.Type == null)
            {
                this.Type = new SymbolType(ArithmeticExpression.Type);
            }
            else
            {
                if (OperandType != null)
                {
                    if (OperandType != ArithmeticExpression.Type)
                    {
                        throw new SyntaticException("Não é possível comparar expressões dos tipos '" + this.OperandType + "' e '" + ArithmeticExpression.Type + "'");
                    }
                }
                else
                {
                    if (this.Type != ArithmeticExpression.Type)
                    {
                        throw new SyntaticException("Não é permitido combinar expressões dos tipos '" + this.Type + "' e '" + ArithmeticExpression.Type + "'.");
                    }
                }
            }
            OperandStack.Push(new BooleanOperand()
            {
                IsTemporary = true,
                TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
            });
        }


        public void BExpressionEnded()
        {
            ArithmeticExpression.BExpressionEnded();
        }

        public void AddBooleanOperator()
        {
            string _operator = Context.CurrentToken;
            debugString += _operator;
            if (this.Type == null)
                this.Type = new SymbolType(TypeValue.boolex);
            if (this.Type.TypeValue != TypeValue.boolex)
            {
                throw new SyntaticException("Não é possível aplicar um operador booleano a uma expressão de tipo '" + this.Type + "'");
            }
            
            this.ArithmeticExpression = new ArithmeticExpression(Context);

            if (_operator == "&&" || _operator == "||" || _operator == "!")
            {
                this.OperandType = new SymbolType(TypeValue.boolex);
            }

            if (_operator == "&&")
                GenerateAndOperator();
            else if (_operator == "||")
                GenerateOrOperator();
            else if (_operator == "!")
                GenerateNotOperator();
            else
                throw new InternalSyntaticException("Operador booleano não reconhecido: " + _operator);
        }

        public void AddBooleanLiteral()
        {
            string literal = Context.CurrentToken;
            debugString += literal;
            OperandStack.Push(new BooleanOperand()
            {
                IsConstant = true,
                Value = literal == "verdadex" ? true : false
            });

            if (this.Type == null)
                this.Type = new SymbolType(TypeValue.boolex);
            else if(this.Type.TypeValue != TypeValue.boolex)
                throw new SyntaticException("Não é possível combinar um literal booleano a uma expressão de tipo '" + this.Type + "'");
            this.ArithmeticExpression = new ArithmeticExpression(Context);
        }

        public void AddEqualityOperator()
        {
            string _operator = Context.CurrentToken;
            debugString += _operator;
            OperandType = new SymbolType(this.Type);
            this.Type.TypeValue = TypeValue.boolex;
            this.ArithmeticExpression = new ArithmeticExpression(Context);

            GenerateEqualityOperator(_operator);
        }

        private void GenerateAndOperator()
        {
            if (OperatorStack.Count > 0)
            {
                string stackOp = OperatorStack.Peek();
                if (stackOp == "&&" || stackOp == "!=" || stackOp == "==" || stackOp == "<" ||
                    stackOp == "<=" || stackOp == ">" || stackOp == ">=" || stackOp == "!")
                {
                    GenerateCode();
                    GenerateAndOperator();
                    return;
                }
            }
            OperatorStack.Push("&&");
        }

        private void GenerateOrOperator()
        {
            if (OperatorStack.Count > 0)
            {
                string stackOp = OperatorStack.Peek();
                if (stackOp == "||" || stackOp == "&&" || stackOp == "!=" || stackOp == "==" || stackOp == "<" ||
                    stackOp == "<=" || stackOp == ">" || stackOp == ">=" || stackOp == "!")
                {
                    GenerateCode();
                    GenerateOrOperator();
                    return;
                }
            }
            OperatorStack.Push("||");
        }

        private void GenerateEqualityOperator(string _operator)
        {
            if (OperatorStack.Count > 0)
            {
                string stackOp = OperatorStack.Peek();
                if ( stackOp == "==" || stackOp == "!=" || stackOp == "<" || stackOp == "<=" || stackOp == ">" 
                    || stackOp == ">=" || stackOp == "!")
                {
                    GenerateCode();
                    GenerateEqualityOperator(_operator);
                    return;
                }
            }
            OperatorStack.Push(_operator);
        }

        private void GenerateComparisonOperator(string _operator)
        {
            if (OperatorStack.Count > 0)
            {
                string stackOp = OperatorStack.Peek();
                if (stackOp == "<" || stackOp == "<=" || stackOp == ">"
                    || stackOp == ">=" || stackOp == "!")
                {
                    GenerateCode();
                    GenerateComparisonOperator(_operator);
                    return;
                }
            }
            OperatorStack.Push(_operator);
        }

        private void GenerateNotOperator()
        {
            OperatorStack.Push("!");
        }

        public void AddComparisonOperator()
        {
            string _operator = Context.CurrentToken;
            debugString += _operator;
            if (this.Type.TypeValue != TypeValue.intex)
            {
                throw new SyntaticException("Os operadores de comparação só podem ser aplicados a expressões do tipo 'intex'");
            }
            this.OperandType = new SymbolType(TypeValue.intex);
            this.Type.TypeValue = TypeValue.boolex;
            this.ArithmeticExpression = new ArithmeticExpression(Context);

            GenerateComparisonOperator(_operator);
        }

        private void GenerateCode()
        {
            string _operator = OperatorStack.Pop();
            if (_operator == "!")
            {
                BooleanOperand operand = OperandStack.Pop();
                Context.CommandBlockStack.Current.CodeGenerator.AddBooleanOperation(operand, "!");
            }
            else
            {
                BooleanOperand second = OperandStack.Pop();
                BooleanOperand first = OperandStack.Pop();
                Context.CommandBlockStack.Current.CodeGenerator.AddBooleanOperation(first, second, _operator);
            }

            OperandStack.Push(new BooleanOperand()
            {
                IsTemporary = true,
                TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
            });
        }

        public override string ToString()
        {
            return debugString;
        }

        public void StartFunctionCall()
        {
            ArithmeticExpression.StartFunctionCall();
        }

        public void EndFunctionCall()
        {
            ArithmeticExpression.EndFunctionCall();
        }

        public void EndExpression()
        {
            if (OperatorStack.Count > 0)
            {
                GenerateCode();
                EndExpression();
            }
            if (OperandStack.Count > 0)
            {
                Context.CommandBlockStack.Current.CodeGenerator.AddBooleanOperation(OperandStack.Pop());
                OperandStack.Push(new BooleanOperand()
                {
                    IsTemporary = true,
                    TempLabel = Context.CommandBlockStack.Current.CodeGenerator.GetLatestTempLabel()
                });
            }
        }
    }
}
